<?php


namespace app\common;


use app\Exception\UserException;
use app\utils\redis\RedisCache;
use app\utils\redis\RedisObj;
use Firebase\JWT\JWT;
use think\facade\Cache;
use think\facade\Env;
use think\Request;

class Token
{
    /**
     * 校验token是否有效
     * @param Request $request
     * @return mixed
     */
    static public function checkToken(Request $request)
    {
        $token = $request->header('authorization');
        if (!$token) {
            throw new UserException(40003, '请填写token');
        }
        return self::checkByCache($token);
    }

    /**
     * 取消当前的token
     * @param Request $request
     * @return bool
     */
    static public function cancelToken(Request $request)
    {
        $token = $request->header('authorization');
        $arr = self::decodeToken($token);
        try {
            $key = self::buildKey($arr['info_arr']['user_id'], $arr['info_arr']['email']);
            self::removeOldToken($key);
            return true;
        }catch (\Exception $e){
            return false;
        }
    }

    /**
     * 保存token
     * @param string $email 邮箱
     * @param int $user_id 用户id
     * @return string
     */
    static public function setToken(string $email, int $user_id): string
    {
        return self::setTokenByCache($email, $user_id);
    }

    /**
     * 通过token获取user_id
     * @param Request $request
     * @return int
     */
    static public function getUserIdByToken() {
        $token = \request()->header('authorization');
        $arr = self::decodeToken($token);
        return $arr['info_arr']['user_id'];
    }

    /**
     * 删除旧的token
     * @param string $key
     * @param bool $isRedis
     */
    static private function removeOldToken(string $key, bool $isRedis = true)
    {
        if($isRedis){
            RedisCache::dDeleteKey(RedisObj::TOKEN_DB, $key);
        }else{
            Cache::delete($key);
        }
    }

    /** 生成key
     * @param string $id
     * @param string $email
     * @return string
     */
    static public function buildKey(string $id, string $email)
    {
        return "token_{$id}_{$email}";
    }

    // 如果登陆接口调用了两次，最好的话就是在数据库保存上次的token，然后取出上次的token，在redis
    // 或者本地缓存找，看看是否存在，存在则覆盖
    static private function setTokenByCache(string $email, int $user_id, bool $isRedis = true): string
    {
        $key = self::buildKey($user_id, $email);
        self::removeOldToken($key);
        $jwtKey = Env::get('jwt.key');
        $nowTime = time();
        $payload = array(
            "email" => $email,
            "user_id" => $user_id,
            "start_time" => $nowTime,
            "end_time" => $nowTime + Env::get('jwt.timestamp'),
        );
        $jwtToken = JWT::encode($payload, $jwtKey);
        if($isRedis){
            RedisCache::wSetValue(RedisObj::TOKEN_DB, $key, $jwtToken, Env::get('jwt.timesecond', 7200));
        }else {
            Cache::set($key, $jwtToken, Env::get('jwt.timesecond', 7200));
        }
        return $jwtToken;
    }

    // 本地存储的话先解密token，再查找本地是否存在
    static private function checkByCache(string $jwtToken, bool $isRedis = true)
    {
        $arr = self::decodeToken($jwtToken);
        $decoded = $arr['info_arr'];
        $jwtArr = $arr['token'];
        $key = self::buildKey($decoded['user_id'], $decoded['email']);
        if($isRedis) {
            $re = RedisCache::rGetValue(RedisObj::TOKEN_DB, $key);
        }else{
            $re = Cache::get($key, false);
        }
        if ($re != $jwtArr[1] || !$re) {
            throw new UserException(40003, '无效token');
        }
        return $re;
    }


    // 解密token
    static private function decodeToken(string $jwtToken): array
    {
        try {
            $jwtArr = explode(' ', $jwtToken);
            if ($jwtArr && count($jwtArr) == 2) {
                $decoded = (array)JWT::decode($jwtArr[1], Env::get('jwt.key'), array('HS256'));
                return [
                    'token' => $jwtArr,
                    'info_arr' => $decoded
                ];
            }
            throw new UserException(40003, '无效token');
        } catch (\Exception $e) {
            throw new UserException(40003, '无效token');
        }
    }

    // redis存储的话，先查找token是否存在redis中再解密token
}